package libs

import (
	"bytes"
	"crypto/rand"
	"crypto/rsa"
	"crypto/sha1"
	"crypto/sha256"
	"crypto/sha512"
	"crypto/x509"
	"encoding/binary"
	"encoding/hex"
	"encoding/json"
	"encoding/pem"
	"errors"
	"fmt"
	"github.com/astaxie/beego/config"
	"hash"
	"io"
	"io/ioutil"
	"log"
	"mime/multipart"
	"net/http"
	"os"
	"strconv"
	"strings"
	"time"

	//m "portrait/models"

	"github.com/astaxie/beego"
	"golang.org/x/crypto/pkcs12"
)

/*
 * 需要注释掉 golang.org/x/crypto/pkcs12/pkcs12.go 中的如下代码
 *
 * 	if len(authenticatedSafe) != 2 {
 * 		return nil, nil, NotImplementedError("expected exactly two items in the authenticated safe")
 * 	}
 */

type EncryptController struct {
	beego.Controller
}

/*
 * 读取pfx文件中的私钥
 */
func readPrivateKey(filename, password string) (*rsa.PrivateKey, int64, error) {
	var (
		pfxData []byte
		key     interface{}
		cert    *x509.Certificate
		err     error
	)
	if pfxData, err = ioutil.ReadFile(filename); err != nil {
		return nil, 0, err
	}
	if key, cert, err = pkcs12.Decode(pfxData, password); err != nil {
		return nil, 0, err
	}
	privateKey := key.(*rsa.PrivateKey)
	if err = privateKey.Validate(); err != nil {
		return nil, 0, err
	}
	return privateKey, cert.SerialNumber.Int64(), nil
}

/*
 * 读取cer文件中的公钥
 */
func readPublickKey(filename string) (*rsa.PublicKey, error) {
	var (
		pemData []byte
		block   *pem.Block
		cert    *x509.Certificate
		err     error
	)
	if pemData, err = ioutil.ReadFile(filename); err != nil {
		return nil, err
	}
	if block, _ = pem.Decode(pemData); block == nil {
		return nil, errors.New("解析pem文件失败")
	}
	if cert, err = x509.ParseCertificate(block.Bytes); err != nil {
		return nil, err
	}

	return cert.PublicKey.(*rsa.PublicKey), nil
}

/*
 * 计算数据校验和
 */
func getDataHash(data []byte, timestamp int64, method string) []byte {
	var hash hash.Hash
	switch method {
	case "1.0.0.e1":
		hash = sha1.New()
	case "1.0.0.e2":
		hash = sha256.New()
	case "1.0.0.e3":
		hash = sha512.New()
	default:
		hash = sha256.New()
	}

	buf := bytes.NewBuffer(data)
	// 大端写入时间戳
	binary.Write(buf, binary.BigEndian, timestamp)
	// 计算校验和
	hash.Write(buf.Bytes())
	return hash.Sum(nil)
}

/**
*计时平台pfx证书解析-私钥加密-生成请求地址
 */

func encrypt(funcname string, data []byte, reqtype int64, province, city string) (string, error) {
	var (
		headurl      string //三方请求地址
		serialnumber int64
		hashed       []byte
		signed       []byte
		key          *rsa.PrivateKey
		err          error
	)
	conf, err := config.NewConfig("ini", "conf/app.conf")
	if err != nil {
		log.Println("配置文件载入错误")
	}

	certSection, err := conf.GetSection("cert")
	if err != nil {
		log.Println("配置文件载入错误-数据库-section载入失败")
	}

	certPath := certSection["cert_key"]
	certPass := certSection["cert_pass"]
	//log.Println(certSections)
	// certPath := beego.AppConfig.String("cert_key")
	// certPass := beego.AppConfig.String("cert_pass")
	//获取省平台地址和全国驾培平台地址
	if reqtype == 1 {
		headurl = certSection["country_url"]
	} else {
		section, err := conf.GetSection(province)
		if err != nil {
			log.Println(err, province)
			log.Println("配置文件载入错误-数据库-section载入失败")
		}
		if province == "130000" && city != "130100" {
			headurl = section["province_url"]
		} else if province == "130000" && city == "130100" {
			headurl = section["city_url"]
		} else if province == "140000" {
			headurl = section["province_url"]
		} else if province == "510000" {
			headurl = section["province_url"]
		} else if province == "410000" {
			headurl = section["province_url"]
		} else if province == "420000" {
			if city == "420100" && reqtype == 3 {
				headurl = section["city_url"]
			} else {
				headurl = section["province_url"]
			}
		} else if province == "520000" {
			headurl = section["province_url"]
		} else if province == "110000" {
			headurl = section["province_url"]
		} else if province == "450000" {
			headurl = section["province_url"]
		} else if province == "630000" {
			headurl = section["province_url"]
		} else if province == "370000" {
			headurl = section["province_url"]
		}
	}
	encryptVer := certSection["encrypt_ver"]
	//log.Println(headurl, encryptVer)
	if key, serialnumber, err = readPrivateKey(certPath, certPass); err != nil {
		return "", err
	}
	// 时间戳
	timestamp := time.Now().UnixNano() / 1e6
	//fmt.Println(timestamp)
	// 第一步  加密数据hash运算
	if hashed = getDataHash(data, timestamp, encryptVer); hashed == nil {
		return "", errors.New("计算校验和失败")
	}
	// 第二步 进行rsa加密签名--私钥加密
	if signed, err = rsa.SignPKCS1v15(rand.Reader, key, 0, hashed); err != nil {
		return "", err
	}
	// 第三步 hex编码
	signStr := strings.ToUpper(hex.EncodeToString(signed))
	if beego.BConfig.RunMode == "xinjiang" {
		return fmt.Sprintf("%s%s?v=%s&ts=%d&sign=%s&user=%s", headurl, funcname, encryptVer, timestamp, signStr, strings.ToUpper(fmt.Sprintf("%x", serialnumber))), nil
	} else if province == "130000" && city == "130100" {
		return fmt.Sprintf("%s%s?v=%s&ts=%d&sign=%s&user=%s", headurl, funcname, encryptVer, timestamp, signStr, "157C2D524B8"), nil
	} else if city == "420100" {
		return fmt.Sprintf("%s%s?v=%s&ts=%d&sign=%s&user=%X", headurl, funcname, encryptVer, timestamp, signStr, serialnumber), nil
	}
	return fmt.Sprintf("%s%s?v=%s&ts=%d&sign=%s&user=%x", headurl, funcname, encryptVer, timestamp, signStr, serialnumber), nil
}

/**
* 获取接口请求地址（带签名验证）
 */
func GetSignURL(funcname string, data map[string]interface{}, reqtype int64, province, city string) (string, error) {
	var (
		body []byte
		err  error
	)
	if data == nil {
		params := strings.Split(funcname, "-")
		if len(params) == 2 {
			return encrypt(funcname, []byte(params[1]), reqtype, province, city)
		} else if len(params) == 3 {
			return encrypt(funcname, []byte(params[0]+params[2]), reqtype, province, city)
		}
	} else if body, err = json.Marshal(data); err != nil {
		return "", err
	} else {
		return encrypt(funcname, body, reqtype, province, city)
	}
	return "", errors.New("生成请求地址失败 " + funcname)
}

func WuhanSignReq(funcname string, data map[string]interface{}, req_type int64, procince, city string, method string) (err error) {
	var (
		url     string
		ret_map map[string]interface{}
	)
	if url, err = GetSignURL(funcname, data, req_type, procince, city); err != nil {
		return err
	}
	if ret_map, err = ApiReq(url, data, method); err != nil {
		return err
	}
	//Logfile(url, "log.student.log")
	//Logfile(data, "log.student.log")
	log.Println(ret_map)
	switch ret_map["errorcode"].(type) {
	case float64:
		if ret_map["errorcode"].(float64) != 0 {
			return errors.New(ret_map["message"].(string))
		}
	case string:
		if ret_map["errorcode"].(string) != "0" {
			return errors.New(ret_map["message"].(string))
		}
	}
	return nil
}

// 写入文件
func Logfile(data interface{}, file_name string) {
	var f1 *os.File
	var err1 error

	Filenames := "./" + file_name //也可将name作为参数传进来

	if checkFile(Filenames) { //如果文件存在
		f1, err1 = os.OpenFile(Filenames, os.O_APPEND|os.O_WRONLY, 0666) //打开文件,第二个参数是写入方式和权限
		if err1 != nil {
			fmt.Println("文件存在，已打开")
		}
	} else {
		f1, err1 = os.Create(Filenames) //创建文件
		if err1 != nil {
			fmt.Println("创建文件失败")
		}
	}
	logger := log.New(f1, "", log.Ldate|log.Ltime|log.Lshortfile)
	logger.Println(data)

}

func checkFile(Filename string) bool {
	var exist = true
	if _, err := os.Stat(Filename); os.IsNotExist(err) {
		exist = false
		if err != nil {
			fmt.Println("检查文件失败")
		}
	}
	return exist
}

/**
*接口请求
 */
func ApiReq(url string, data map[string]interface{}, method string) (map[string]interface{}, error) {
	var (
		jsondata []byte
		body     []byte
		request  *http.Request
		response *http.Response
		err      error
	)
	if jsondata, err = json.Marshal(data); err != nil {
		return nil, err
	}
	//fmt.Println("请求地址", url, "请求的值：", string(jsondata))
	if request, err = http.NewRequest(method, url, bytes.NewBuffer(jsondata)); err != nil {
		return nil, err
	}
	request.Header.Set("Content-Type", "application/json; charset=UTF-8")
	http.DefaultClient.Timeout = 45 * time.Second
	if response, err = http.DefaultClient.Do(request); err != nil {
		return nil, err
	}
	defer request.Body.Close() //*注意关闭流
	if body, err = ioutil.ReadAll(response.Body); err != nil {
		return nil, err
	}
	//fmt.Println("返回的值", string(body))

	// 记录数据库
	// (&m.ApiLog{
	// 	Url:    url,
	// 	Method: method,
	// 	Param:  string(jsondata),
	// 	Result: string(body)}).Add()

	ret_map := make(map[string]interface{})
	//fmt.Println(ret_map, "----------------")
	if err = json.Unmarshal(body, &ret_map); err != nil {
		return nil, err
	}
	return ret_map, nil
}

/**
*文件资料上传编码
 */
func Imageup(f io.Reader, imgtype, filename string, reqtype int64, province, city string) (float64, []byte, error) {
	var (
		part     io.Writer
		rsp      *http.Response
		data     []byte
		ret_byte []byte
		uri      string
		err      error
		id       float64
	)
	if data, err = ioutil.ReadAll(f); err != nil {
		return 0, nil, err
	}
	if uri, err = encrypt("imageup/"+imgtype, data, reqtype, province, city); err != nil {
		return 0, data, err
	}
	//fmt.Println("!!!!")
	body := bytes.NewBuffer(nil)
	writer := multipart.NewWriter(body)
	if part, err = writer.CreateFormFile("file", filename); err != nil {
		return 0, data, err
	}
	part.Write(data)
	writer.Close()
	// 上传数据
	fmt.Println(uri, "request province upload image url")
	//fmt.Println(writer.FormDataContentType())
	// fmt.Println(string(body))
	if rsp, err = http.Post(uri, writer.FormDataContentType(), body); err != nil {
		return 0, data, err
	}
	//fmt.Println("!!!!")
	defer rsp.Body.Close()
	if ret_byte, err = ioutil.ReadAll(rsp.Body); err != nil {
		return 0, data, err
	}
	// fmt.Println("!!!!")
	ret_map := make(map[string]interface{})
	if err = json.Unmarshal(ret_byte, &ret_map); err != nil {
		return 0, data, err
	}
	//fmt.Println(ret_map)
	//	fmt.Println("!!!!")
	switch ret_map["errorcode"].(type) {
	case float64:
		if ret_map["errorcode"].(float64) != 0 {
			return 0, data, errors.New(ret_map["message"].(string))
		}
	case string:
		if ret_map["errorcode"].(string) != "0" {
			return 0, data, errors.New(ret_map["message"].(string))
		}
	}
	//fmt.Println("!!!!")
	if md, ok := ret_map["data"].(map[string]interface{}); ok {
		switch md["id"].(type) {
		case float64:
			return md["id"].(float64), data, nil
		case string:
			id, _ = strconv.ParseFloat(md["id"].(string), 64)
			return id, data, nil
		}

	}
	//fmt.Println("!!!!")
	return 0, data, errors.New("获取文件编号失败")
}
